package pt.iscte.osgi.contracts.aspects;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

import pt.iscte.osgi.contracts.annotations.NotSubclassByClients;
import pt.iscte.osgi.contracts.annotations.RequiredConstructor;
import pt.iscte.osgi.contracts.annotations.RequiresConstant;
import pt.iscte.osgi.contracts.annotations.Singleton;


public abstract aspect ClassRequirements {

	private pointcut exclude() : !within(pt.iscte.osgi.contracts.aspects.*+);
			
	private ArrayList<Class<?>> objectsCreated = new ArrayList<Class<?>>();
	

	before(): staticinitialization(*) && exclude() {
		Class<?> type = thisJoinPoint.getSignature().getDeclaringType();
		if(!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
			for(Class<?> interf: type.getInterfaces()){				
				for(Annotation annotation: interf.getAnnotations()){
					if(annotation instanceof RequiredConstructor){
						boolean classHasConstrutor = false;
						for(Constructor<?> constructor: type.getDeclaredConstructors()){
							classHasConstrutor = classHasConstrutor || hasConstructor(constructor, annotation);
						}
						if(!classHasConstrutor){
							throwRequiredConstructorException(type, ((RequiredConstructor) annotation).value());
						}
					} else {
						if (annotation instanceof RequiresConstant) {
							boolean classHasField = false;
							for (Field field : type.getDeclaredFields()) {
								classHasField = classHasField || hasField(field, annotation);
							}
							if (!classHasField) {
//								System.out.println(type.getSimpleName() + " should have one constant: final " + type + " " + ((RequiresConstant) annotation).name());
								throw new IllegalStateException(type.getSimpleName() + " should have one constant: final " + type + " " + ((RequiresConstant) annotation).name());
							}
						}
					}
				}
			}
			Class<?> superclasse = type.getSuperclass();
			if(!superclasse.equals(Object.class)){
				for(Annotation ann: superclasse.getAnnotations()){
					if(ann instanceof NotSubclassByClients){
//						System.out.println("class not extensive from " + superclasse.getSimpleName());
						throw new UnsupportedOperationException("class not extensive from " + superclasse.getSimpleName());
					}
				}
			}
		
		}
	}
	
	private boolean hasConstructor(Constructor<?> constructor, Annotation annotation){
		Class<?>[] paramConstructor = constructor.getParameterTypes();
		Class<?>[] paramAnnotation = ((RequiredConstructor) annotation).value();
		
		if(paramConstructor.length != paramAnnotation.length) return false;
		
		for(int i = 0; i != paramConstructor.length; i++){
			if(!paramConstructor[i].equals(paramAnnotation[i])){
				return false;	
			}
		}				
		
		return true;
	}
	
	private boolean hasField(Field field, Annotation annotation){
		Class<?> type = ((RequiresConstant) annotation).type();
		String name = ((RequiresConstant) annotation).name();		
		return field.getName().equals(name) && field.getType().equals(type) && Modifier.isFinal(field.getModifiers());
	}
	
	private void throwRequiredConstructorException(Class<?> classe, Class<?>[] param){
		String parameters = "";
		for(int i = 0; i != param.length; i++){
			parameters += param[i].getSimpleName();
			if(i != param.length-1){
				parameters += ", ";
			}
		}
		
//		System.out.println(classe.getSimpleName() + " should have the constructor {" + parameters + "}");
		throw new IllegalStateException(classe.getSimpleName() + " should have the constructor {" + parameters + "}");
	}
	
		before(): call(*.new(..)) && exclude()  {
		Class<?> type = thisJoinPoint.getSignature().getDeclaringType();
		
		//nivel classe
		for(Annotation ann: type.getAnnotations()){
			if(ann instanceof Singleton){
				if(objectsCreated.contains(type)){
//					System.out.println("can only create one instance of this class");
						throw new SecurityException("can only create one instance of this class");
				} else {
					objectsCreated.add(type);
				}
			}
		}
		
		//nivel interface
		if(!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
			for(Class<?> interf: type.getInterfaces()){				
				for(Annotation ann: interf.getAnnotations()){
					if(ann instanceof Singleton){
						if(objectsCreated.contains(type)){
//							System.out.println("can only create one instance of this class");
								throw new SecurityException("can only create one instance of this class");
						} else {
							objectsCreated.add(type);
						}
					}
				}
			}
		}
		
	}
}
